/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
*/

package com.chasekernan.hxnova.core.stars;

import haxe.xml.Check;

/**
    This class holds the environment of a star system, and the names of the 
    basic enviornment types.
**/
class Environment {
    
    /**
        Holds the basic environment types (gravity, temperature, and radiation
        in Stars!
    **/
    public static var ENV_TYPES : Array<String> = ["gravity", "temperature", 
                                                   "radiation"];
                                                   
    public static var GRAVITY_AND_TEMP_PROB = 
           [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 10, 10, 10, 10, 10, 10,
            10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
            10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
            10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
            10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 
            10, 10, 10, 10, 10, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1];
            
    private static var GRAVITY_AND_TEMP_SUM = calcSum();
    private static function calcSum() : Int {
        var sum = 0;
        for (num in GRAVITY_AND_TEMP_PROB) sum += num;
        return sum;
    }
    
    /**
        The minimum value of any one environment type. Note that this does not
        exactly correlate with the displayed values, as those are multiplied and
        displayed in different units (like 100C or 4.2g).
    **/
    public static var MIN_VALUE : Int = 0;
    
    /**
        The maximum value of an environment type.
    **/
    public static var MAX_VALUE : Int = 100;
    
    /**
        Returns the center of the environment range (i.e. (min + max) / 2).
    **/
    public static function getCenter() : Int {
        return Std.int((MAX_VALUE + MIN_VALUE) / 2);
    }
    
    public var values : Array<Int>;
    
    /**
        All values are randomized unless random is specified as false, then
        they're set to the center of the range.
    **/
    public function new (?random = true) {
        values = new Array<Int>();
        if (!random) {
            for(i in 0...ENV_TYPES.length) values.push(getCenter());
        } else randomize();
    }
    
    public function randomize() {
        //gravity and temperature are the same
        setValue(Environment.ENV_TYPES[0], getRandomGravOrTemp());
        setValue(Environment.ENV_TYPES[1], getRandomGravOrTemp());
        
        //radiation is a little different (not much of a curve)
        setValue(Environment.ENV_TYPES[2], Std.random(100) + 1);
    } 
    
    private function getRandomGravOrTemp() : Int {
        var rand = Std.random(GRAVITY_AND_TEMP_SUM) + 1;
        
        var sum = 0;
        for (i in 0...GRAVITY_AND_TEMP_PROB.length) {
            var value = GRAVITY_AND_TEMP_PROB[i];
            
            if (sum < rand && (sum + value) >= rand) return i;
            sum += value;
        }
        
        //shouldn't every be here
        throw "Error while trying to gen a random environment.";
    }
    
    /**
        Returns the value of an environment type as an int.
        Throws an error if there is no such environment type.
    **/
    public function getValue(type : String) : Int {
        for(i in 0...ENV_TYPES.length) {
            if(ENV_TYPES[i] == type) return values[i];
        }
        throw "No such environment type: " + type;
    }
    
    public static var XML_TITLE = "environment";
    
    public static function getXmlRules() : Rule {
        var nameArray  = new Array<Rule>();
        for(i in ENV_TYPES) nameArray.push(Rule.RNode(i, [Att('value')]));
        
        return Rule.RNode(XML_TITLE, [], Rule.RList(nameArray, false));
    }
    
    /**
        Sets the value of a specific environment type.
        Throws an error if the value is out of bounds or there is no such
        environment type.
    **/
    public function setValue(type : String, value : Int) {
        for(i in 0...ENV_TYPES.length) {
            if(ENV_TYPES[i] == type) {
                if(checkValue(value)) values[i] = value;
                else throw "Environment value is out of range: " + value; 
                return;
            }
        }
        
        //if here no such value
        throw "No such environment type: " + type;
    }
    
    /**
        Writes an environment to xml.
        Sample XML:
            <environment>
                <gravity value="67">
                <temperature value="43">
                <radiation value="54">
            </environment>
    **/
    public static function writeToXml(e : Environment) : Xml {
        var xml : Xml = Xml.parse('<' + XML_TITLE + '></' + XML_TITLE +'>')
                                .firstChild();
        
        for(i in ENV_TYPES) {
            var childXml = Xml.createElement(i);
            childXml.set('value', Std.string(e.getValue(i)));
            xml.addChild(childXml);
        }
        //check it 
        Check.checkNode(xml, getXmlRules());
        return xml;
    }
    
    /**
        Reads an enviroment from xml. It will throw an error if there are any 
        mistakes. Note that the xml files read purely will return a document
        element, but this needs the first child (or element) of an xml document.
    **/
    public static function readFromXml(xml : Xml) : Environment {
        try {
            var e = new Environment();
            //check type
            //Check.checkNode(xml, getXmlRules());
            
            for(i in xml.elements()) 
                e.setValue(i.nodeName, Std.parseInt(i.get('value')));
                
            return e;
        } catch (e : Dynamic) {
            throw "Bad environment file: " + e;
        }
    }
    
    public function iterator () {
        return values.iterator();
    }
    
    /**
        Checks to see if the value supplied is within range.
    **/
    public static function checkValue(value : Int) : Bool {
        return (value <= MAX_VALUE && value >= MIN_VALUE);
    }
}

#if unit
import haxe.unit.TestCase;

class EnvironmentTest extends haxe.unit.TestCase {
    public var env : Environment;
    
    public function testConcentrations() {
        env = new Environment(false);
        
        assertEquals(Std.int(
                (Environment.MAX_VALUE + Environment.MIN_VALUE) / 2), 
                Environment.getCenter());
        
        for (envType in Environment.ENV_TYPES) {
            assertEquals(Environment.getCenter(), env.getValue(envType));
        }
        
        for (value in env) {
            assertEquals(value, Environment.getCenter());
        }
        
        env = new Environment();
        
        for (i in 0...100) {
            for (value in env) {
                assertTrue(value >= Environment.MIN_VALUE && 
                        value <= Environment.MAX_VALUE);
            }
        }
        
        try {
            env.getValue("gibberish");
            assertTrue(false);
        } catch (e : Dynamic) {
            assertTrue(true);
        }
        
        try {
            env.setValue("gibberish", 45);
            assertTrue(false);
        } catch (e : Dynamic) {
            assertTrue(true);
        }
        
        try {
            env.setValue(Environment.ENV_TYPES[0], -12);
            assertTrue(false);
        } catch (e : Dynamic) {
            assertTrue(true);
        }
        
        env.setValue(Environment.ENV_TYPES[1], 64);
        assertEquals(env.getValue(Environment.ENV_TYPES[1]), 64);
    }
    
    public function testEnvironmentXml() {
        env = new Environment();
        
        var xml = Environment.writeToXml(env);
        for (element in xml.elements()) {
            assertEquals(Std.parseInt(element.get("value")), 
                         env.getValue(element.nodeName));
        }
        
        var readValues = Environment.readFromXml(
                Environment.writeToXml(env));
        for (envType in Environment.ENV_TYPES) {
            assertEquals(env.getValue(envType), readValues.getValue(envType));
        }
    }
}

#end